package nsqlookupd

import (
	"fmt"
	"log"
	"net"
	"os"
	"sync"

	"github.com/nsqio/nsq/internal/http_api"
	"github.com/nsqio/nsq/internal/lg"
	"github.com/nsqio/nsq/internal/protocol"
	"github.com/nsqio/nsq/internal/util"
	"github.com/nsqio/nsq/internal/version"
)

/*管理拓扑信息并提供最终一致性的发现服务*/
type NSQLookupd struct {
	sync.RWMutex	// 读写锁
	opts         *Options // 配置
	tcpListener  net.Listener //监听的tcp服务
	httpListener net.Listener // 监听的http服务
	waitGroup    util.WaitGroupWrapper //用于等待goroutine结束
	DB           *RegistrationDB // 注册数据库
}

func New(opts *Options) *NSQLookupd {
	if opts.Logger == nil {
		opts.Logger = log.New(os.Stderr, opts.LogPrefix, log.Ldate|log.Ltime|log.Lmicroseconds)
	}
	n := &NSQLookupd{
		opts: opts,
		DB:   NewRegistrationDB(),
	}

	var err error
	opts.logLevel, err = lg.ParseLogLevel(opts.LogLevel, opts.Verbose)
	if err != nil {
		n.logf(LOG_FATAL, "%s", err)
		os.Exit(1)
	}

	n.logf(LOG_INFO, version.String("nsqlookupd"))
	return n
}

// Main 启动nsqlookupd的实例，如果出现问题，则返回一个错误。
func (this *NSQLookupd) Main() error {

	// ctx中包含nsqlookup的所有配置信息
	ctx := &Context{this}
	// 启动tcp服务
	tcpListener, err := net.Listen("tcp", this.opts.TCPAddress)
	if err != nil {
		return fmt.Errorf("listen (%s) failed - %s", this.opts.TCPAddress, err)
	}
	// 启动http服务
	httpListener, err := net.Listen("tcp", this.opts.HTTPAddress)
	if err != nil {
		return fmt.Errorf("listen (%s) failed - %s", this.opts.TCPAddress, err)
	}

	this.tcpListener = tcpListener
	this.httpListener = httpListener

	tcpServer := &tcpServer{ctx: ctx}

	// 封装的waitGroup，内部使用goroutine启动该服务，使用waitGroup守护改协程直到退出
	//tcpServer接到TCP数据时，调用其Handle方法(见nsqlookupd\tcp.go)来处理
	this.waitGroup.Wrap(func() {
		protocol.TCPServer(tcpListener, tcpServer, this.logf)
	})
	httpServer := newHTTPServer(ctx)
	this.waitGroup.Wrap(func() {
		http_api.Serve(httpListener, httpServer, "HTTP", this.logf)
	})

	return nil
}

func (this *NSQLookupd) RealTCPAddr() *net.TCPAddr {
	return this.tcpListener.Addr().(*net.TCPAddr)
}

func (this *NSQLookupd) RealHTTPAddr() *net.TCPAddr {
	return this.httpListener.Addr().(*net.TCPAddr)
}

func (this *NSQLookupd) Exit() {
	if this.tcpListener != nil {
		this.tcpListener.Close()
	}

	if this.httpListener != nil {
		this.httpListener.Close()
	}
	this.waitGroup.Wait()
}
